فارسی

قدرت ساختارهای داده تغییرناپذیر در تایپ‌اسکریپت را با انواع readonly کشف کنید. بیاموزید چگونه با جلوگیری از تغییرات ناخواسته داده، اپلیکیشن‌هایی قابل پیش‌بینی‌تر، قابل نگهداری‌تر و قوی‌تر بسازید.

انواع Readonly در تایپ‌اسکریپت: تسلط بر ساختارهای داده تغییرناپذیر

در چشم‌انداز همواره در حال تحول توسعه نرم‌افزار، تلاش برای دستیابی به کدی قوی، قابل پیش‌بینی و قابل نگهداری یک کوشش دائمی است. تایپ‌اسکریپت، با سیستم نوع‌بندی قوی خود، ابزارهای قدرتمندی برای رسیدن به این اهداف فراهم می‌کند. در میان این ابزارها، انواع readonly به عنوان یک مکانیزم حیاتی برای اعمال تغییرناپذیری (immutability) برجسته می‌شوند که سنگ بنای برنامه‌نویسی تابعی و کلیدی برای ساخت اپلیکیشن‌های قابل اعتمادتر است.

تغییرناپذیری (Immutability) چیست و چرا اهمیت دارد؟

تغییرناپذیری در اصل به این معناست که پس از ایجاد یک شیء، وضعیت آن قابل تغییر نیست. این مفهوم ساده پیامدهای عمیقی برای کیفیت و قابلیت نگهداری کد دارد.

انواع Readonly در تایپ‌اسکریپت: زرادخانه تغییرناپذیری شما

تایپ‌اسکریپت چندین راه برای اعمال تغییرناپذیری با استفاده از کلمه کلیدی readonly فراهم می‌کند. بیایید تکنیک‌های مختلف و نحوه استفاده از آنها در عمل را بررسی کنیم.

۱. خصوصیات Readonly در اینترفیس‌ها و تایپ‌ها

مستقیم‌ترین راه برای تعریف یک خصوصیت به عنوان readonly، استفاده مستقیم از کلمه کلیدی readonly در تعریف یک اینترفیس یا تایپ است.


interface Person {
  readonly id: string;
  name: string;
  age: number;
}

const person: Person = {
  id: "unique-id-123",
  name: "Alice",
  age: 30,
};

// person.id = "new-id"; // Error: Cannot assign to 'id' because it is a read-only property.
person.name = "Bob"; // This is allowed

در این مثال، خصوصیت id به صورت readonly تعریف شده است. تایپ‌اسکریپت از هرگونه تلاش برای تغییر آن پس از ایجاد شیء جلوگیری می‌کند. خصوصیات name و age که فاقد اصلاحگر readonly هستند، می‌توانند آزادانه تغییر کنند.

۲. تایپ کمکی Readonly

تایپ‌اسکریپت یک تایپ کمکی قدرتمند به نام Readonly<T> ارائه می‌دهد. این تایپ عمومی، یک تایپ موجود T را می‌گیرد و با readonly کردن تمام خصوصیات آن، آن را تغییر می‌دهد.


interface Point {
  x: number;
  y: number;
}

const point: Readonly<Point> = {
  x: 10,
  y: 20,
};

// point.x = 30; // Error: Cannot assign to 'x' because it is a read-only property.

تایپ Readonly<Point> یک نوع جدید ایجاد می‌کند که در آن هم x و هم y به صورت readonly هستند. این یک راه راحت برای تغییرناپذیر کردن سریع یک تایپ موجود است.

۳. آرایه‌های Readonly (ReadonlyArray<T>) و readonly T[]

آرایه‌ها در جاوااسکریپت ذاتاً قابل تغییر هستند. تایپ‌اسکریپت راهی برای ایجاد آرایه‌های readonly با استفاده از تایپ ReadonlyArray<T> یا شکل کوتاه آن readonly T[] فراهم می‌کند. این کار از تغییر محتویات آرایه جلوگیری می‌کند.


const numbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
// numbers.push(6); // Error: Property 'push' does not exist on type 'readonly number[]'.
// numbers[0] = 10; // Error: Index signature in type 'readonly number[]' only permits reading.

const moreNumbers: readonly number[] = [6, 7, 8, 9, 10]; // Equivalent to ReadonlyArray
// moreNumbers.push(11); // Error: Property 'push' does not exist on type 'readonly number[]'.

تلاش برای استفاده از متدهایی که آرایه را تغییر می‌دهند، مانند push، pop، splice، یا تخصیص مستقیم به یک ایندکس، منجر به خطای تایپ‌اسکریپت خواهد شد.

۴. const در مقابل readonly: درک تفاوت

مهم است که بین const و readonly تمایز قائل شویم. const از تخصیص مجدد خود متغیر جلوگیری می‌کند، در حالی که readonly از تغییر خصوصیات شیء جلوگیری می‌کند. آنها اهداف متفاوتی دارند و می‌توانند برای حداکثر تغییرناپذیری با هم استفاده شوند.


const immutableNumber = 42;
// immutableNumber = 43; // Error: Cannot reassign to const variable 'immutableNumber'.

const mutableObject = { value: 10 };
mutableObject.value = 20; // This is allowed because the *object* is not const, just the variable.

const readonlyObject: Readonly<{ value: number }> = { value: 30 };
// readonlyObject.value = 40; // Error: Cannot assign to 'value' because it is a read-only property.

const constReadonlyObject: Readonly<{ value: number }> = { value: 50 };
// constReadonlyObject = { value: 60 }; // Error: Cannot reassign to const variable 'constReadonlyObject'.
// constReadonlyObject.value = 60; // Error: Cannot assign to 'value' because it is a read-only property.

همانطور که در بالا نشان داده شد، const تضمین می‌کند که متغیر همیشه به همان شیء در حافظه اشاره می‌کند، در حالی که readonly تضمین می‌کند که وضعیت داخلی شیء بدون تغییر باقی می‌ماند.

مثال‌های عملی: به کارگیری انواع Readonly در سناریوهای واقعی

بیایید چند مثال عملی از نحوه استفاده از انواع readonly برای بهبود کیفیت و قابلیت نگهداری کد در سناریوهای مختلف را بررسی کنیم.

۱. مدیریت داده‌های پیکربندی

داده‌های پیکربندی اغلب یک بار در هنگام راه‌اندازی اپلیکیشن بارگیری می‌شوند و نباید در طول اجرا تغییر کنند. استفاده از انواع readonly تضمین می‌کند که این داده‌ها ثابت باقی می‌مانند و از تغییرات تصادفی جلوگیری می‌کند.


interface AppConfig {
  readonly apiUrl: string;
  readonly timeout: number;
  readonly features: readonly string[];
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  features: ["featureA", "featureB"],
};

function fetchData(url: string, config: Readonly<AppConfig>) {
    // ... use config.timeout and config.apiUrl safely, knowing they won't change
}

fetchData("/data", config);

۲. پیاده‌سازی مدیریت وضعیت شبه-Redux

در کتابخانه‌های مدیریت وضعیت مانند Redux، تغییرناپذیری یک اصل اساسی است. می‌توان از انواع readonly برای اطمینان از اینکه وضعیت تغییرناپذیر باقی می‌ماند و اینکه reducerها فقط اشیاء وضعیت جدید را برمی‌گردانند به جای تغییر موارد موجود، استفاده کرد.


interface State {
  readonly count: number;
  readonly items: readonly string[];
}

const initialState: State = {
  count: 0,
  items: [],
};

function reducer(state: Readonly<State>, action: { type: string; payload?: any }): State {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 }; // Return a new state object
    case "ADD_ITEM":
      return { ...state, items: [...state.items, action.payload] }; // Return a new state object with updated items
    default:
      return state;
  }
}

۳. کار با پاسخ‌های API

هنگام دریافت داده از یک API، اغلب مطلوب است که با داده‌های پاسخ به عنوان داده‌های تغییرناپذیر رفتار شود، به خصوص اگر از آن برای رندر کردن کامپوننت‌های UI استفاده می‌کنید. انواع readonly می‌توانند به جلوگیری از تغییرات تصادفی داده‌های API کمک کنند.


interface ApiResponse {
  readonly userId: number;
  readonly id: number;
  readonly title: string;
  readonly completed: boolean;
}

async function fetchTodo(id: number): Promise<Readonly<ApiResponse>> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
  const data: ApiResponse = await response.json();
  return data;
}

fetchTodo(1).then(todo => {
  console.log(todo.title);
  // todo.completed = true; // Error: Cannot assign to 'completed' because it is a read-only property.
});

۴. مدل‌سازی داده‌های جغرافیایی (مثال بین‌المللی)

نمایش مختصات جغرافیایی را در نظر بگیرید. هنگامی که یک مختصات تنظیم شد، در حالت ایده‌آل باید ثابت بماند. این امر یکپارچگی داده‌ها را تضمین می‌کند، به ویژه هنگام کار با برنامه‌های حساس مانند سیستم‌های نقشه‌برداری یا ناوبری که در مناطق مختلف جغرافیایی (مثلاً مختصات GPS برای یک سرویس تحویل که آمریکای شمالی، اروپا و آسیا را پوشش می‌دهد) کار می‌کنند.


interface GeoCoordinates {
 readonly latitude: number;
 readonly longitude: number;
}

const tokyoCoordinates: GeoCoordinates = {
 latitude: 35.6895,
 longitude: 139.6917
};

const newYorkCoordinates: GeoCoordinates = {
 latitude: 40.7128,
 longitude: -74.0060
};


function calculateDistance(coord1: Readonly<GeoCoordinates>, coord2: Readonly<GeoCoordinates>): number {
 // Imagine complex calculation using latitude and longitude
 // Returning placeholder value for simplicity
 return 1000; 
}

const distance = calculateDistance(tokyoCoordinates, newYorkCoordinates);
console.log("Distance between Tokyo and New York (placeholder):", distance);

// tokyoCoordinates.latitude = 36.0; // Error: Cannot assign to 'latitude' because it is a read-only property.

انواع Readonly عمیق: مدیریت اشیاء تو در تو

تایپ کمکی Readonly<T> فقط خصوصیات مستقیم یک شیء را readonly می‌کند. اگر یک شیء حاوی اشیاء یا آرایه‌های تو در تو باشد، آن ساختارهای تو در تو قابل تغییر باقی می‌مانند. برای دستیابی به تغییرناپذیری عمیق واقعی، باید به صورت بازگشتی Readonly<T> را به تمام خصوصیات تو در تو اعمال کنید.

در اینجا مثالی از نحوه ایجاد یک تایپ readonly عمیق آورده شده است:


type DeepReadonly<T> = T extends (infer R)[]
  ? DeepReadonlyArray<R>
  : T extends object
  ? DeepReadonlyObject<T>
  : T;

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
    country: string;
  };
  employees: string[];
}

const company: DeepReadonly<Company> = {
  name: "Example Corp",
  address: {
    street: "123 Main St",
    city: "Anytown",
    country: "USA",
  },
  employees: ["Alice", "Bob"],
};

// company.name = "New Corp"; // Error
// company.address.city = "New City"; // Error
// company.employees.push("Charlie"); // Error

این تایپ DeepReadonly<T> به صورت بازگشتی Readonly<T> را به تمام خصوصیات تو در تو اعمال می‌کند و تضمین می‌کند که کل ساختار شیء تغییرناپذیر است.

ملاحظات و بده‌بستان‌ها

در حالی که تغییرناپذیری مزایای قابل توجهی ارائه می‌دهد، مهم است که از بده‌بستان‌های بالقوه آن آگاه باشید.

کتابخانه‌هایی برای ساختارهای داده تغییرناپذیر

چندین کتابخانه می‌توانند کار با ساختارهای داده تغییرناپذیر در تایپ‌اسکریپت را ساده‌تر کنند:

بهترین شیوه‌ها برای استفاده از انواع Readonly

برای بهره‌برداری مؤثر از انواع readonly در پروژه‌های تایپ‌اسکریپت خود، این بهترین شیوه‌ها را دنبال کنید:

نتیجه‌گیری: پذیرش تغییرناپذیری با انواع Readonly در تایپ‌اسکریپت

انواع readonly در تایپ‌اسکریپت ابزاری قدرتمند برای ساخت اپلیکیشن‌های قابل پیش‌بینی‌تر، قابل نگهداری‌تر و قوی‌تر هستند. با پذیرش تغییرناپذیری، می‌توانید خطر باگ‌ها را کاهش دهید، اشکال‌زدایی را ساده‌تر کنید و کیفیت کلی کد خود را بهبود بخشید. اگرچه بده‌بستان‌هایی برای در نظر گرفتن وجود دارد، مزایای تغییرناپذیری اغلب بر هزینه‌ها می‌چربد، به ویژه در پروژه‌های پیچیده و طولانی‌مدت. همانطور که سفر تایپ‌اسکریپت خود را ادامه می‌دهید، انواع readonly را به بخش مرکزی گردش کار توسعه خود تبدیل کنید تا پتانسیل کامل تغییرناپذیری را آزاد کرده و نرم‌افزاری واقعاً قابل اعتماد بسازید.